const std = @import("std");

pub const Error = error{
    PipeClosed,
    OutOfMemory,
};

//LockedFifo create , thread safe locked fifo type
pub fn LockedFifo(comptime T: type, comptime cache_size: isize) type {
    var sz1 = cache_size;
    if (sz1 <= 0) {
        sz1 = 10;
    }
    const buf_size: isize = @intCast(sz1);

    return struct {
        const FifoType = std.fifo.LinearFifo(T, .{ .Static = @intCast(buf_size) });
        fifo: FifoType = FifoType.init(),
        semw: std.Thread.Semaphore = .{ .permits = @intCast(buf_size) },
        semr: std.Thread.Semaphore = .{ .permits = 0 },
        mutex: std.Thread.Mutex = .{},
        isClosed: bool = false,

        const Self = @This();

        //writeItem write 1 item, thread safe
        pub fn writeItem(self: *Self, item: T) !void {
            self.semw.wait();
            self.mutex.lock();
            defer self.mutex.unlock();
            if (self.isClosed) {
                return Error.PipeClosed;
            }
            try self.fifo.writeItem(item);
            self.semr.post();
            std.Thread.yield() catch {};
        }

        //readItem get 1 item, thread safe
        pub fn readItem(self: *Self) !T {
            self.semr.wait();
            self.mutex.lock();
            defer self.mutex.unlock();
            if (self.isClosed and self.fifo.count == 0) {
                return Error.PipeClosed;
            }
            const ret = self.fifo.readItem();
            self.semw.post();

            if (ret != null) {
                return ret.?;
            } else {
                return Error.OutOfMemory;
            }
        }

        //deinit release memory
        pub fn deinit(self: *Self) void {
            self.fifo.deinit();
        }

        //close close fifo, thread safe
        pub fn close(self: *Self) void {
            self.mutex.lock();
            if (!self.isClosed) {
                self.isClosed = true;
                self.semw.post();
                self.semr.post();
            }

            self.mutex.unlock();
        }
    };
}
